﻿using System;
using System.Xml;
using Windows.Foundation;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using LinkPad.Helpers;

namespace LinkPad.Shapes
{
    class ManagedImage : ManagedShape
    {
        #region Fields

        private double _realX;
        private double _realY;
        private double _realWidth;
        private double _realHeight;
        private readonly double _refPtX; // 0 to 1
        private readonly double _refPtY; // 0 to 1

        private const double OpacitySelected = 0.5;
        private const double OpacityUnselected = 1.0;

        #endregion

        #region Constructors

        public ManagedImage(ShapeManager shapeManager, double refPtX = 0.5, double refPtY = 0.5)
            : base(shapeManager)
        {
            _refPtX = refPtX;
            _refPtY = refPtY;

            InnerImage = new Image {Stretch = Stretch.Uniform};
            InnerImage.SizeChanged += (sender, args) => ImageChanged();
            InnerImage.ImageOpened += (sender, args) => ImageChanged();
        }

        #endregion

        #region Properties

        #region ManagedShape members

        public override Rect BoundingBox
        {
            get
            {
                var rect = new Rect(Left, Top, ActualWidth, ActualHeight);
                return rect;
            }
        }

        public override Rect RealBoundingBox
        {
            get
            {
                var rect = new Rect(RealLeft, RealTop, _realWidth, _realHeight);
                return rect;
            }
        }

        #endregion

        public Image InnerImage { get; private set; }

        public double X
        {
            get { return Left + ActualWidth * _refPtX; }
            set
            {
                ShapeManager.ShapeChanged(this);
                _realX = ShapeManager.GetAbsoluteLocationX(value);
                if (Math.Abs(X - value) < double.Epsilon) return;
                Left = value - ActualWidth * _refPtX;
            }
        }

        public double Y
        {
            get { return Top + ActualHeight * _refPtY; }
            set
            {
                ShapeManager.ShapeChanged(this);
                _realY = ShapeManager.GetAbsoluteLocationY(value);
                if (Math.Abs(Y - value) < double.Epsilon) return;
                Top = value - ActualHeight * _refPtY;
            }
        }

        public double Left
        {
            get
            {
                return (double)InnerImage.GetValue(Canvas.LeftProperty);
            }
            private set
            {
                InnerImage.SetValue(Canvas.LeftProperty, value);
            }
        }

        public double Right
        {
            get { return Left + ActualWidth; }
        }

        public double Bottom
        {
            get { return Top + ActualHeight; }
        }

        public double Top
        {
            get
            {
                return (double) InnerImage.GetValue(Canvas.TopProperty);
            }
            private set
            {
                InnerImage.SetValue(Canvas.TopProperty, value);
            }
        }

        public double ActualWidth
        {
            get { return InnerImage.ActualWidth; }
        }

        public double ActualHeight
        {
            get { return InnerImage.ActualHeight; }
        }

        public double Width
        {
            get { return InnerImage.Width; }
            set
            {
                var diff = value - InnerImage.Width;
                InnerImage.Width = value;
                var diffLeft = diff*_refPtX;
                Left -= diffLeft;
                UpdateRealSize();
            }
        }

        public double Height
        {
            get { return InnerImage.Height; }
            set
            {
                var diff = value - InnerImage.Height;
                InnerImage.Height = value;
                var diffTop = diff*_refPtY;
                Top -= diffTop;
                UpdateRealSize();
            }
        }

        private double RealLeft
        {
            get { return _realX - _realWidth * _refPtX; }
        }

        private double RealTop
        {
            get { return _realY - _realHeight * _refPtY; }
        }

        #endregion

        #region Methods

        #region ManagedShape members

        public override void AddToCanvas()
        {
            ShapeManager.Canvas.Children.Add(InnerImage);
        }

        public override void RemoveFromCanvas()
        {
            ShapeManager.Canvas.Children.Remove(InnerImage);
        }

        public override int[] IndicesInCanvas()
        {
            var index = ShapeManager.Canvas.Children.IndexOf(InnerImage);
            return new[] {index};
        }

        public override void InsertToCanvas(int index)
        {
            ShapeManager.Canvas.Children.Insert(index, InnerImage);
        }

        public override double DistanceToPoint(Point point)
        {
            var x = point.X;
            var y = point.Y;
            if (x < Left)
            {
                if (y < Top)
                {
                    return GeometryHelper.Distance(x, y, Left, Top);
                }
                if (y > Bottom)
                {
                    return GeometryHelper.Distance(x, y, Left, Bottom);
                }
                return Left - x;
            }
            if (x > Right)
            {
                if (y < Top)
                {
                    return GeometryHelper.Distance(x, y, Right, Top);
                }
                if (y > Bottom)
                {
                    return GeometryHelper.Distance(x, y, Right, Bottom);
                }
                return x - Right;
            }
            if (y < Top)
            {
                return Top - y;
            }
            if (y > Bottom)
            {
                return y - Bottom;
            }
            return 0;
        }

        public override void Transform()
        {
            var scaleX = ShapeManager.ScaleX;
            var scaleY = ShapeManager.ScaleY;
            var offsetX = ShapeManager.OffsetX;
            var offsetY = ShapeManager.OffsetY;

            InnerImage.Width = _realWidth * scaleX;
            InnerImage.Height = _realHeight * scaleY;

            var x = _realX * scaleX + offsetX;
            var y = _realY * scaleY + offsetY;

            Left = x - ActualWidth * _refPtX;
            Top = y - ActualHeight * _refPtY;
        }

        public override void WriteToXml(XmlWriter xmlWriter)
        {
            throw new NotImplementedException();
        }

        public override bool ReadFromXml(XmlReader xmlReader)
        {
            throw new NotImplementedException();
        }

        #endregion

        public void Select()
        {
            InnerImage.Opacity = OpacitySelected;
        }

        public void Deselect()
        {
            InnerImage.Opacity = OpacityUnselected;
        }

        public void LoadFromImageSource(IRandomAccessStream fileStream)
        {
            var bitmap = new BitmapImage();
            var asyncAction = bitmap.SetSourceAsync(fileStream);
            asyncAction.Completed += (info, status) =>
            {
                InnerImage.Width = bitmap.PixelWidth;
                InnerImage.Height = bitmap.PixelHeight;
            };
            InnerImage.Source = bitmap;
        }

        private void UpdateRealSize()
        {
            _realWidth = ShapeManager.GetAbsoluteWidth(InnerImage.ActualWidth);
            _realHeight = ShapeManager.GetAbsoluteHeight(InnerImage.ActualHeight);

            ShapeManager.ShapeChanged(this);
        }

        private void ImageChanged()
        {
            UpdateRealSize();
            Transform();
        }

        #endregion
    }
}
